package admin;


import generation.CodeGenerator;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintStream;
import java.io.Reader;
import java.util.ArrayList;

import lexical.Scanner;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;

import semantic.SemanticAnalyzer;
import syntax.Parser;
import astnode.DeclarationNode;

import common.LogPrinter;
import common.TokenType;
/**
 * 
 * @author Yunke Li Boshi Li 
 * @version 1.1
 * Thanks for Ashton
 */
public class Admin {
	/**
	 * Parse the args from the commandline to setup the options for 
	 * your program. Once Completed create an instance of Scanner
	 * to use the arguments provided.
	 * 
	 * Currently accepted commandline arguments should be:
	 * 
	 * -l | --log  Enables logging
	 * 
	 * Example:
	 * java Main test.txt                // Tokenizes test.txt without logging
	 * java Main test1.txt test2.txt     // Tokenizes test1.txt and test2.txt
	 * java Main -l test.txt             // Tokenizes test.txt with log to Screen
	 * java Main -l log.txt test.txt     // Tokenizes test.txt with log to log.txt
	 * java Main --log=log.txt -err=err.txt test.txt  // Tokenizes test.txt with log to log.txt with
	 */
	
	private ArrayList<String> spellingTable = new ArrayList<String>();
	private LogPrinter logPrinter = null;
	private PrintStream errPrinter = new PrintStream(System.err);//new ErrPrinter( spellingTable );
	private PrintStream astPrinter = null;
	private PrintStream quadPrinter = null;
	private PrintStream tknPrinter = null;
	private Scanner scanner = null;
	private Parser parser = null;
	private SemanticAnalyzer sem = null;
	private CodeGenerator generator = null;

	/**
	 * Initial components of compilers
	 */
	public Admin(){
		try {
			quadPrinter = new PrintStream("quadruple.txt");
			astPrinter = new PrintStream("ast.txt");
			logPrinter =  new LogPrinter( new PrintStream( "log.txt" ) );
			tknPrinter = new PrintStream("token.txt");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Close and flush printer
	 */
	public void close(){
		logPrinter.close();
		errPrinter.close();
		quadPrinter.close();
		astPrinter.close();
		tknPrinter.close();
	}
	
	/**
	 * Analyse one file. Compiler will terminate at some phase which meet an error
	 * @param reader 
	 * @param phase 
	 * @throws Exception Can not read the input file
	 */
	public void analysis(Reader reader, String phase ) throws Exception{	
		
		scanner = new Scanner( spellingTable, logPrinter, errPrinter , tknPrinter );
		parser = new Parser( scanner, logPrinter , errPrinter , astPrinter, spellingTable );
		ArrayList< DeclarationNode > ASTRoot = parser.getAstRoot();
		sem = new SemanticAnalyzer(ASTRoot, logPrinter, errPrinter , astPrinter, spellingTable );
		generator = new CodeGenerator(ASTRoot, quadPrinter , logPrinter, spellingTable );
				
		scanner.setReader(reader);
		
		logPrinter.println("");
		logPrinter.println("LEXICAL AND SYNTAX ANALYSIS BEGIN:");
		
		//for the condition when user want just to print tokens
		if( phase.equals("lex") ){
			while( scanner.nextToken().getType() != TokenType.ENDFILE );
			scanner.printLastCode();
			return;
		}
		//syntax analysis begin
		parser.program();	
		//if syntax analysis find errors, stop right away
		if( parser.containsErr() ){
			logPrinter.println("Lexical and Syntax analysis found errors, analysis teminate");
			errPrinter.println("Lexical and Syntax analysis found errors, analysis teminate");
			return;
		}else{
			logPrinter.println("Syntax analysis done");
		}
		//if user set phrase 'sem', that means user wants to print semantic AST, so we can not print syntax AST now. Else we will print AST
		if( !phase.equals("sem") ){
			parser.printASTree();
		}
		//if user set phrase 'syn', stop right away
		if( phase.equals("syn")){
			return;
		}
		
		logPrinter.println("");
		logPrinter.println("SEMANTIC ANALYSIS BEGIN:");
		
		sem.analyse();
		if( sem.containErr() ){
			logPrinter.println("Semantic analysis found errors, analysis teminate");
			errPrinter.println("Semantic analysis found errors, analysis teminate");
			return;
		}else{
			logPrinter.println("Semantic analysis done");
		}
		sem.printAST();
		
		if( phase.equals("sem"))		return;
		
		logPrinter.println("");
		logPrinter.println("CODE GENERATION BEGIN:");
		generator.analyse();		
	}
	
	public static void main(String[] args){
		String sourceFile = "";
		
		if( args.length >=1&&!args[0].contains("-") ){
			sourceFile = args[0];
		}
		
		Admin m = new Admin();
		try{
			Options options = new Options();
			Option help = new Option("help", "show this message");
			
			OptionBuilder.withArgName("file");
			OptionBuilder.hasOptionalArg();
			OptionBuilder.withDescription( "use given file for log" );
			Option logOpt =  OptionBuilder.create( "log" );
			
			OptionBuilder.withArgName("file");
			OptionBuilder.hasArg();
			OptionBuilder.withValueSeparator();
			OptionBuilder.withDescription( "use given file for error" );
			Option errOpt = OptionBuilder.create( "err" );
			
			OptionBuilder.withArgName("file");
			OptionBuilder.hasOptionalArg();
			OptionBuilder.withDescription( "use given file for output quadruple" );
			Option tupleOpt =OptionBuilder.create( "quad" );
			
			OptionBuilder.withArgName("file");
			OptionBuilder.hasOptionalArg();
			OptionBuilder.withDescription( "use given file for output ast" );
			Option astOpt =OptionBuilder.create( "ast" );
			
			OptionBuilder.withArgName("phase");
			OptionBuilder.hasOptionalArg();
			OptionBuilder.withDescription( "will terminate at given phase" );
			Option phaseOpt =OptionBuilder.create( "phase" );
			
			OptionBuilder.withArgName("file");
			OptionBuilder.hasOptionalArg();
			OptionBuilder.withDescription( "use given file for output tokens" );
			Option tokenOpt =OptionBuilder.create( "tkn" );
			
			options.addOption( logOpt );
			options.addOption( errOpt );
			options.addOption( tupleOpt );
			options.addOption( astOpt );
			options.addOption( phaseOpt );
			options.addOption( tokenOpt );
			options.addOption( help );

			CommandLineParser comLineParser = new BasicParser();
			CommandLine line = comLineParser.parse( options , args );
			
			if( line.hasOption("log")){
				String log = line.getOptionValue( "log" );
				if( log==null ){
					m.logPrinter = new LogPrinter( ( new PrintStream( System.out ) ) );
				}else{
					m.logPrinter = new LogPrinter( ( new PrintStream( log ) ) );
				}
			}
			
			if( line.hasOption("err")){
				m.errPrinter =  new PrintStream( line.getOptionValue( "err" ) ); 
			}else if(line.hasOption("log")){
				m.errPrinter = new PrintStream("error.txt");
			}
			
			if( line.hasOption("help")){
				HelpFormatter helpFor = new HelpFormatter();
				helpFor.printHelp("C*13 Compiler Options: <file> <options>", options );
			}
			
			if( line.hasOption("quad") ){
				String quad = line.getOptionValue("quad");
				if( quad==null ){
					m.quadPrinter = new PrintStream( System.out );
				}else{
					m.quadPrinter = new PrintStream( quad ) ;
				}				
			}
			
			if( line.hasOption("ast")){
				String ast = line.getOptionValue("ast");
				if( ast==null ){
					m.astPrinter = new PrintStream( System.out );
				}else{
					m.astPrinter = new PrintStream(ast);
				}		
			}
			
			if( line.hasOption("tkn")){
				String tkn = line.getOptionValue("tkn");
				if( tkn==null ){
					m.tknPrinter = new PrintStream( System.out );
				}else{
					m.tknPrinter = new PrintStream(tkn);
				}	
			}
			
			String phase = "";
			if( line.hasOption("phase")){
				phase = line.getOptionValue("phase");
				if( phase.equals("lex") && ( line.hasOption("ast") || line.hasOption("quad") )){
					throw new Exception("scanner can not product ast and quad");
				}else if( phase.equals("syn") && line.hasOption("quad") ){
					throw new Exception("parser can not product quad");
				}else if( phase.equals("sem") && line.hasOption("quad") ){
					throw new Exception("semantic analyzer can not product quad");
				}else if( !phase.equals("gen") && !phase.equals("lex") && !phase.equals("syn") && !phase.equals("sem")){
					throw new Exception("can not recognise phase");
				}
			}
			
			if( sourceFile.equals("" )){
				return;
			}
			
			Reader reader = new FileReader( sourceFile );
			m.analysis( reader , phase );
			m.close();
			
		}catch (Exception e) {
			e.printStackTrace();
			m.errPrinter.println(e.toString());
			m.close();			
		}
	}

}
